/*Diego Lima de Freitas RA: 317632
 *Yuri Souza Padua RA: 317420
 * 
 * Classe corresponde ao analisador léxico, com nextToken() entre outros
 */
package Lexer;

import compiler.CompilerError;
import java.util.Hashtable;

public class Lexer {

    // token atual
    public int token;
    private String stringValue;
    private int numberValue;
    private char charValue;
    private int tokenPos;
    //  input[lastTokenPos] último caracter do ultimo token encontrado
    private int lastTokenPos;
    //  input[beforeLastTokenPos] último caracter do token antes do ultimo token encontrado
    private int beforeLastTokenPos;
    // program given as input - source code
    private char[] input;
    // Número da linha atual, começa com 1
    private int lineNumber;
    private CompilerError error;
    private static final int MaxValueInteger = 32768;

    public Lexer(char[] input, CompilerError error) {
        this.input = input;
        // fim do arquivo
        input[input.length - 1] = '\0';
        lineNumber = 1;
        tokenPos = 0;
        lastTokenPos = 0;
        beforeLastTokenPos = 0;
        this.error = error;
    }

    public void skipBraces() {
        // pula qualquer símbolo [ ] { } ( )
        if (token == Symbol.CURLYLEFTBRACE || token == Symbol.CURLYRIGHTBRACE
                || token == Symbol.LEFTSQBRACKET || token == Symbol.RIGHTSQBRACKET) {
            nextToken();
        }
        if (token == Symbol.EOF) {
            error.signal("EOF inesperado");
        }
    }

    public void skipPunctuation() {
        // pula qualquer símbolo de pontuação
        while (token != Symbol.EOF && (token == Symbol.COMMA || token == Symbol.SEMICOLON)) {
            nextToken();
        }
        if (token == Symbol.EOF) {
            error.signal("EOF inesperado");
        }
    }

    public void skipTo(int[] arraySymbol) {
        while (token != Symbol.EOF) {
            int i = 0;
            while (i < arraySymbol.length) {
                if (token == arraySymbol[i]) {
                    return;
                } else {
                    i++;
                }
            }
            nextToken();
        }
        if (token == Symbol.EOF) {
            error.signal("EOF inesperado");
        }
    }

    public void skipToNextStatement() {

        while (token != Symbol.EOF && token != Symbol.ELSE && token != Symbol.END && token != Symbol.SEMICOLON) {
            nextToken();
        }
        if (token == Symbol.SEMICOLON) {
            nextToken();
        }
    }
    // Contém as palavras-chave da linguagem
    static private Hashtable keywordsTable;

    // Este código será executado somnente uma vez
    static {
        keywordsTable = new Hashtable();
        keywordsTable.put("var", new Integer(Symbol.VAR));
        keywordsTable.put("begin", new Integer(Symbol.BEGIN));
        keywordsTable.put("end", new Integer(Symbol.END));
        keywordsTable.put("if", new Integer(Symbol.IF));
        keywordsTable.put("then", new Integer(Symbol.THEN));
        keywordsTable.put("else", new Integer(Symbol.ELSE));
        keywordsTable.put("print", new Integer(Symbol.PRINT));
        keywordsTable.put("integer", new Integer(Symbol.INTEGER));
        keywordsTable.put("boolean", new Integer(Symbol.BOOLEAN));
        keywordsTable.put("true", new Integer(Symbol.TRUE));
        keywordsTable.put("false", new Integer(Symbol.FALSE));
        keywordsTable.put("and", new Integer(Symbol.AND));
        keywordsTable.put("or", new Integer(Symbol.OR));
        keywordsTable.put("not", new Integer(Symbol.NOT));
        keywordsTable.put("proc", new Integer(Symbol.PROCEDURE));
        keywordsTable.put("for", new Integer(Symbol.FOR));
        keywordsTable.put("while", new Integer(Symbol.WHILE));
        keywordsTable.put("to", new Integer(Symbol.TO));
        keywordsTable.put("do", new Integer(Symbol.DO));
        keywordsTable.put("return", new Integer(Symbol.RETURN));
        keywordsTable.put("getInteger", new Integer(Symbol.GETINTEGER));



    }

    public void nextToken() {
        char ch;

        while ((ch = input[tokenPos]) == ' ' || ch == '\r' || ch == '\t' || ch == '\n') {
            // Conta o numero de linhas
            if (ch == '\n') {
                lineNumber++;
            }
            tokenPos++;
        }
        if (ch == '\0') {
            token = Symbol.EOF;
        } else if (input[tokenPos] == '#') {
            // Encontra comentário
            while (input[tokenPos] != '\0' && input[tokenPos] != '\n') {
                tokenPos++;
            }
            nextToken();
        } else {
            if (Character.isLetter(ch)) {
                // pega identificador ou palavra chave
                StringBuffer ident = new StringBuffer();
                while (Character.isLetter(input[tokenPos]) || Character.isDigit(input[tokenPos])) {
                    ident.append(input[tokenPos]);
                    tokenPos++;
                }
                stringValue = ident.toString();
                // se está na lista de palavras chave então é uma palavra chave
                Object value = keywordsTable.get(stringValue);
                if (value == null) {
                    token = Symbol.IDENT;
                } else {
                    token = ((Integer) value).intValue();
                }
            } else if (Character.isDigit(ch)) {
                // Pega o numero
                StringBuffer number = new StringBuffer();
                while (Character.isDigit(input[tokenPos])) {
                    number.append(input[tokenPos]);
                    tokenPos++;
                }
                token = Symbol.NUMBER;
                try {
                    numberValue = Integer.valueOf(number.toString()).intValue();
                } catch (NumberFormatException e) {
                    error.signal("Numero fora dos limites");
                }
                if (numberValue >= MaxValueInteger) {
                    error.signal("Numero fora dos limites");
                }
            } else {
                tokenPos++;
                switch (ch) {
                    case '+':
                        token = Symbol.PLUS;
                        break;
                    case '-':
                        token = Symbol.MINUS;
                        break;
                    case '*':
                        token = Symbol.MULT;
                        break;
                    case '/':
                        token = Symbol.DIV;
                        break;
                    case '%':
                        token = Symbol.REMAINDER;
                        break;
                    case '<':
                        if (input[tokenPos] == '=') {
                            tokenPos++;
                            token = Symbol.LE;
                        } else if (input[tokenPos] == '>') {
                            tokenPos++;
                            token = Symbol.NEQ;
                        } else {
                            token = Symbol.LT;
                        }
                        break;
                    case '>':
                        if (input[tokenPos] == '=') {
                            tokenPos++;
                            token = Symbol.GE;
                        } else {
                            token = Symbol.GT;
                        }
                        break;
                    case '=':
                        if (input[tokenPos] == '=') {
                            tokenPos++;
                            token = Symbol.EQ;
                        } else {
                            token = Symbol.ASSIGN;
                        }
                        break;
                    case '(':
                        token = Symbol.LEFTPAR;
                        break;
                    case ')':
                        token = Symbol.RIGHTPAR;
                        break;
                    case ',':
                        token = Symbol.COMMA;
                        break;
                    case ';':
                        token = Symbol.SEMICOLON;
                        break;

                    case '\'':
                        token = Symbol.CHARACTER;
                        charValue = input[tokenPos];
                        tokenPos++;
                        if (input[tokenPos] != '\'') {
                            error.signal("Caracter ilegal: " + input[tokenPos - 1]);
                        }
                        tokenPos++;
                        break;
                    
                    case '{':
                        token = Symbol.CURLYLEFTBRACE;
                        break;
                    case '}':
                        token = Symbol.CURLYRIGHTBRACE;
                        break;
                    case '[':
                        token = Symbol.LEFTSQBRACKET;
                        break;
                    case ']':
                        token = Symbol.RIGHTSQBRACKET;
                        break;
                    default:
                        error.signal("Caracter invalido: '" + ch + "'");
                }
            }
        }
        beforeLastTokenPos = lastTokenPos;
        lastTokenPos = tokenPos - 1;
    }

    public int getLineNumber() {
        return lineNumber;
    }

    public int getLineNumberBeforeLastToken() {
        return getLineNumber(beforeLastTokenPos);
    }

    private int getLineNumber(int index) {
        
        int i, n, size;
        n = 1;
        i = 0;
        size = input.length;
        while (i < size && i < index) {
            if (input[i] == '\n') {
                n++;
            }
            i++;
        }
        return n;
    }

    public String getCurrentLine() {
        return getLine(lastTokenPos);
    }

    public String getLineBeforeLastToken() {
        return getLine(beforeLastTokenPos);
    }

    private String getLine(int index) {
        
        int i = index;
        if (i == 0) {
            i = 1;
        } else if (i >= input.length) {
            i = input.length;
        }

        StringBuffer line = new StringBuffer();
        
        while (i >= 1 && input[i] != '\n') {
            i--;
        }
        if (input[i] == '\n') {
            i++;
        }
        while (input[i] != '\0' && input[i] != '\n' && input[i] != '\r') {
            line.append(input[i]);
            i++;
        }
        return line.toString();
    }

    public String getStringValue() {
        return stringValue;
    }

    public int getNumberValue() {
        return numberValue;
    }

    public char getCharValue() {
        return charValue;
    }
}
